/*
 * Copyright (C) Francisco Morero Peyrona. All rights reserved.
 *
 * This software is published under the terms of Open Source
 * License version 1.1, a copy of which has been included with this
 * distribution in the License.txt file.
 */

package jdbcmanager.diagram.swing;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

/**
 * This class is similar to JDesktopPane but works with JBoard and JConnector
 * instead of working with JInternalFrame
 *
 * @author Francisco Morero Peyrona
 */

public class JDiagramPane extends JLayeredPane
{
   private final static Integer nCONNECTORS_LAYER = new Integer( DEFAULT_LAYER.intValue() + 1 );
   private final static Integer nBOARDS_LAYER     = new Integer( DEFAULT_LAYER.intValue() + 2 );
   private final static Integer nMAP_LAYER        = new Integer( DEFAULT_LAYER.intValue() + 3 );

   private   JBoard      board     = null;   // Selected board
   private   JConnector  connector = null;   // Selected connector
   private   JDiagramMap map       = null;   // Map (can be visible or not)

   protected JViewport   vpParent  = null;
   protected JPopupMenu  popupMenu = null;   // To be sat by sub-classes

   //-------------------------------------------------------------------------//

   /**
    * Class constructor
    */
   public JDiagramPane( JViewport vpParent )
   {
      this.vpParent = vpParent;
      this.map      = new JDiagramMap();
      
      this.map.setBounds( 0,0,120,120 );
      super.add( this.map, nMAP_LAYER );
      
      setOpaque( true );
      setBackground( new Color( 255, 242, 218 ) );

      // Add listener for mouse events
      addMouseListener( new MouseAdapter()
      {
         public void mousePressed( MouseEvent evt )
         {
            if( evt.isPopupTrigger() && popupMenu != null )
               popupMenu.show( evt.getComponent(), evt.getX(), evt.getY() );
         }
         
         public void mouseReleased( MouseEvent evt ) 
         {
            if( evt.isPopupTrigger() )
            {
               if( popupMenu != null )
                  popupMenu.show( evt.getComponent(), evt.getX(), evt.getY() );
            }
            else
            {  // If map is not visible or the click is not over the map
               if( ! JDiagramPane.this.isMapVisible() || ! JDiagramPane.this.map.getBounds().contains( evt.getX(), evt.getY() ) )
                  select( JDiagramPane.this );
            }
         }
      } );
    
     // Add listener for JViewport events
     this.vpParent.addChangeListener( new ChangeListener()
     {
        public void stateChanged( ChangeEvent evt ) 
        { 
           map.viewportChanged( (JViewport) evt.getSource() );
        }  
     } );
   }

   /**
    * Shows or hides a map with all boards thumbnails.
    */
   public void setMapVisible( boolean bVisible )
   {
      this.map.setVisible( bVisible );
   }
   
   /**
    * Checks map visibility
    */
   public boolean isMapVisible()
   {
      return this.map.isVisible();
   }

   //-------------------------------------------------------------------------//
   // Common methods to Boards and Connectors
   //-------------------------------------------------------------------------//
   
   /**
    * Needed to be redefined: if passed component is an instance of JBoard or JConnector
    * it is selected y moved to front; otherwise <code>super.add(...)</code> is called.
    */
   public void add( JComponent comp )
   {
      if( comp instanceof DiagramComponent )
      {
         if( comp instanceof JBoard )
         {
            super.add( comp, nBOARDS_LAYER );

            this.map.boardAdded( (JBoard) comp );
            
            select( comp );
            resizeDesktop();
         }
         else
         {
            super.add( comp, nCONNECTORS_LAYER );
            moveToFront( comp );     // It is not needed: I do it for congruency
         }
      }
      else
      {
         super.add( comp );    // Just to maintain compatibility with JLayeredPane
      }
   }
   
   /**
    * Needed to be redefined: if passed component is an instance of JBoard or JConnector
    * it is treated specially.
    */
   public void remove( JComponent comp )
   {
      super.remove( comp );
      
      if( comp instanceof JBoard )
      {
         resizeDesktop();
         this.map.boardRemoved( (JBoard) comp );
      }

      if( comp instanceof DiagramComponent )
         repaint();
   }
   
   /**
    * Needed to be redefined: removes all JBoard (its JConnectors are removed automatically),
    * but do not removes the map.
    */
   public void removeAll()
   {
      JBoard[] aBoard = getAllBoards();
      
      for( int n = 0; n < aBoard.length; n++ )
      {
         super.remove( aBoard[n] );
         this.map.boardRemoved( aBoard[n] );
      }
      
      resizeDesktop();
      repaint();
   }

   //-------------------------------------------------------------------------//
   // Boards related methods
   //-------------------------------------------------------------------------//

   /**
    * Return selected board or <code>null</code> if there is no selected board.
    * 
    * @return Selected board or <code>null</code> if there is no selected board.
    */
   public JBoard getSelectedBoard()
   {
      return this.board;
   }

   /**
    * Return all boards.
    * 
    * @return All boards.
    */
   protected JBoard[] getAllBoards()
   {
      Component[] aComp = getComponentsInLayer( nBOARDS_LAYER.intValue() );
      JBoard[]    aRet  = new JBoard[ aComp.length ];

      System.arraycopy( aComp, 0, aRet, 0, aComp.length );

      return aRet;
   }

   //-------------------------------------------------------------------------//
   // Connector related methods
   //-------------------------------------------------------------------------//

   /**
    * Returns selected connector return or <code>null</code> if there is no selected connector.
    * 
    * @return Selected connector return or <code>null</code> if there is no selected connector.
    */
   public JConnector getSelectedConnector()
   {
      return this.connector;
   }

   //-------------------------------------------------------------------------//
   // Other methods
   //-------------------------------------------------------------------------//
   
   /**
    * Selects either a JBoard, a JConnector or a JDiagramPane
    * 
    * @param comp Which to select
    */
   protected void select( Component comp )
   {
      if( comp == this )
      {
         if( this.board != null )
         {
            this.board.setSelected( false );
            this.map.boardSelected( this.board );
            this.board = null;
         }

         if( this.connector != null )
         {
            this.connector.setSelected( false );
            this.connector = null;
         }
      }
      else if( comp instanceof JBoard )
      {
         if( this.board != comp )
         {
            if( this.board != null )
            {
               this.board.setSelected( false );
               this.map.boardSelected( this.board );
            }

            this.board = (JBoard) comp;
            this.board.setSelected( true );
            this.map.boardSelected( this.board );

            moveToFront( this.board );
         }
      }
      else if( comp instanceof JConnector )
      {
         if( this.connector != comp )
         {
            if( this.connector != null )
               this.connector.setSelected( false );

            this.connector = (JConnector) comp;
            this.connector.setSelected( true );
         }
      }
   }

   /**
    * Resizes the desktop: called when a component is moved to makes it 
    * big enought to hold all components.
    */
   protected void resizeDesktop()
   {
      JBoard[] aBoard = getAllBoards();
   
      int nXmin = 0, nYmin = 0, nXmax = 0, nYmax = 0;
   
      // Finds farest points
      for( int n = 0; n < aBoard.length; n++ )
      {
         if( aBoard[n].isVisible() )
         {
            nXmin = Math.min( nXmin, aBoard[n].getX() );
            nYmin = Math.min( nYmin, aBoard[n].getY() );
            nXmax = Math.max( nXmax, aBoard[n].getX() + aBoard[n].getWidth()  );
            nYmax = Math.max( nYmax, aBoard[n].getY() + aBoard[n].getHeight() );
         }
      }

      // If bottom-left frame is not beyond JDesktop area, Xmax and Ymax will be the area
      Rectangle rVisible = getVisibleRect();
   
      nXmax = Math.max( nXmax, rVisible.width  );
      nYmax = Math.max( nYmax, rVisible.height );

      // Are there frames out of area (up and/or left)?
      if( nXmin < 0 || nYmin < 0 )
      {
         // Re-adjusts all frames to shift negative values to positive (has to shift all frames)
         // Because JDestopPane does not allows global coordinates (negative values)
         for( int n = 0; n < aBoard.length; n++ )
             aBoard[n].setLocation( aBoard[n].getX() + Math.abs( nXmin ), aBoard[n].getY() + Math.abs( nYmin ) );

         if( nXmin < 0 )
            nXmax += Math.abs( nXmin );

         if( nYmin < 0 )
            nYmax += Math.abs( nYmin );
      }
   
      setSize( new Dimension( nXmax, nYmax ) );
      setPreferredSize( getSize() );
      revalidate();
      
      if( nXmin < 0 || nYmin < 0 )
      {
         Rectangle r = new Rectangle( Math.abs( nXmin ), Math.abs( nYmin ), 
                                      getWidth(), getHeight() );
                                       
         this.vpParent.scrollRectToVisible( r ); 
      }
      
      this.map.desktopResized( getSize(), this.vpParent );
   }
}